package com.varian.boot.filter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.Header;
import cn.hutool.json.JSONUtil;
import com.varian.boot.properties.VarianLoggingProperties;
import com.varian.boot.util.WebUtil;
import com.varian.tool.Func;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.List;

/**
 * {@link AbstractRequestLoggingFilter}
 *
 * @author ben
 * @since 2024/7/2
 */
@Slf4j
@RequiredArgsConstructor
public class ServletLogFilter extends OncePerRequestFilter {

    private final VarianLoggingProperties properties;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (CollUtil.anyMatch(properties.getIgnoreUrl(), url -> Func.antPathMatch(url, request.getRequestURI()))) {
            chain.doFilter(request, response);
        } else {
            Instant start = Instant.now();
            RepeatableContentCachingRequestWrapper requestWrapper = new RepeatableContentCachingRequestWrapper(request);
            requestLog(requestWrapper);
            ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
            chain.doFilter(requestWrapper, responseWrapper);
            responseLog(responseWrapper, requestWrapper, start);
        }
    }

    private void responseLog(ContentCachingResponseWrapper response, RepeatableContentCachingRequestWrapper request, Instant begin) {
        StringBuilder builder = StrUtil.builder(300);
        List<Object> params = ListUtil.list(false);
        builder.append("\n=========================[response begin]=========================");
        HttpHeaders headers = new HttpHeaders();
        response.getHeaderNames().forEach(name -> {
            if (CollUtil.isEmpty(properties.getWhiteHeader()) || properties.getWhiteHeader().contains(name)) {
                headers.add(name, request.getHeader(name));
            }
        });
        builder.append("\nHeaders: {}");
        params.add(headers);
        String payload = WebUtil.getResponsePayload(response);
        if (StrUtil.isNotBlank(payload)) {
            builder.append("\nPayload: {}");
            params.add(payload);
        }
        builder.append("\n<== {} {}");
        params.add(response.getStatus());
        params.add(request.getRequestURI());
        String queryString = request.getQueryString();
        if (StrUtil.isNotEmpty(queryString)) {
            builder.append("?{}");
            params.add(queryString);
        }
        builder.append(" ({} ms)");
        Duration duration = Duration.between(begin, Instant.now());
        params.add(duration.toMillis());
        builder.append("\n==========================[response end]==========================");
        log.info(builder.toString(), params.toArray());
    }


    private void requestLog(RepeatableContentCachingRequestWrapper request) {
        StringBuilder builder = StrUtil.builder(300);
        List<Object> params = ListUtil.list(false);
        builder.append("\n=========================[request begin]=========================");
        builder.append("\n==> {} {}");
        params.add(request.getMethod());
        params.add(request.getRequestURI());
        String queryString = request.getQueryString();
        if (StrUtil.isNotEmpty(queryString)) {
            builder.append("?{}");
            params.add(queryString);
        }
        builder.append("\nHeaders: {}");
        HttpHeaders headers = new HttpHeaders();
        request.getHeaderNames().asIterator().forEachRemaining(name -> {
            if (CollUtil.isEmpty(properties.getWhiteHeader()) || properties.getWhiteHeader().contains(name)) {
                headers.add(name, request.getHeader(name));
            }
            if (Header.AUTHORIZATION.getValue().equalsIgnoreCase(name)) {
                headers.add(name, Boolean.TRUE.toString());
            }
        });
        params.add(headers);
        String payload = WebUtil.getRequestPayload(request);
        if (StrUtil.isNotBlank(payload)) {
            builder.append("\nPayload: {}");
            params.add(JSONUtil.parse(payload).toString());
        }
        builder.append("\n==========================[request end]==========================");
        log.info(builder.toString(), params.toArray());
    }

}
